import sys
import logging
from ctypes import *

from bcat.error import InvalidArgumentError, StdError

from bcat.ffi.error import LoadingError
from bcat.ffi.callback_ptr_wrapper import CallbackPtrWrapper
from bcat.ffi.loader import SharedObjectsLoader

from bcat.util.utility import Utility


class ExtCallbackPtrWrapper(CallbackPtrWrapper):
    def __init__(self, cbs, loader, dll, ptr=None):
        super().__init__(cbs, ptr)
        self.__loader = loader
        self.__dll = dll

    def set_loader(self, loader):
        self.__loader = loader
        return self

    def loader(self):
        return self.__loader

    def set_dll(self, dll):
        self.__dll = dll
        return self

    def dll(self):
        return self.__dll


class CSafeApi(object):
    CIPHER_BLOCK_LEN = 16

    def __init__(self):
        if sys.platform.startswith('win'):
            target_name = "libWinSgeSafeApi.dll"
        else:
            target_name = "libapi_cli_safe.so"
        self.__loader, self.__target_dll = self._load_so(target_name)

    def _load_so(self, target_name):
        try:
            if sys.platform.startswith('win'):
                loader = SharedObjectsLoader.from_one(target_name).load()
            else:
                loader = SharedObjectsLoader.from_many(["libeasy.so", "liblogger.so", target_name]).load()
            target_dll = loader.get(target_name)

            self.c_encrypt = target_dll.sge_safe_client_packet_encrypt
            self.c_encrypt.argtypes = [c_void_p, c_void_p, c_void_p, c_uint, c_void_p, c_void_p]
            self.c_encrypt.restype = c_int

            self.c_decrypt = target_dll.sge_safe_client_packet_decrypt
            self.c_decrypt.argtypes = [c_void_p, c_void_p, c_void_p, c_uint, c_void_p, c_void_p]
            self.c_decrypt.restype = c_int

            return loader, target_dll
        except LoadingError as e:
            logging.error(f"load dlls:{target_name} error:{e}")
            raise e

    @staticmethod
    def _check_success(err_code, ctx):
        if err_code == 0:
            return True
        else:
            raise StdError(err_code, ctx)

    @staticmethod
    def _check_bool(value, ctx):
        if value == 1:
            return True
        elif value == 0:
            return False
        else:
            raise StdError(value, ctx)

    ''' Safe Handle Related '''

    class EncCallbacks(Structure):
        _fields_ = [
            ('connect', c_void_p),
            ('disconnect', c_void_p),
            ('reconnect', c_void_p),
            ('reset_connect', c_void_p),
            ('is_connected', c_void_p),
            ('encrypt', c_void_p),
            ('decrypt', c_void_p),
            ('mac', c_void_p),
            ('hash', c_void_p),
            ('sign', c_void_p),
            ('verify_sign', c_void_p),
            ('gen_random', c_void_p),
            ('get_skey', c_void_p),
            ('get_enc_pkey', c_void_p),
            ('free_enc_pkey', c_void_p),
            ('get_sign_pkey', c_void_p),
            ('free_sign_pkey', c_void_p),
            ('ctx', c_void_p),
        ]

        @staticmethod
        def create(name, ctx):
            ext_loader = SharedObjectsLoader.from_one(name).load()
            ext_dll = ext_loader.get(name)
            cbs = CSafeApi.EncCallbacks(
                cast(ext_dll.cb_connect_, c_void_p),
                cast(ext_dll.cb_disconnect_, c_void_p),
                cast(ext_dll.cb_reconnect_, c_void_p),
                cast(ext_dll.cb_reset_connect_, c_void_p),
                cast(ext_dll.cb_is_connected_, c_void_p),
                cast(ext_dll.cb_encrypt_, c_void_p),
                cast(ext_dll.cb_decrypt_, c_void_p),
                cast(ext_dll.cb_mac_, c_void_p),
                cast(ext_dll.cb_hash_, c_void_p),
                cast(ext_dll.cb_sign_, c_void_p),
                cast(ext_dll.cb_verify_sign_, c_void_p),
                cast(ext_dll.cb_gen_random_, c_void_p),
                cast(ext_dll.cb_get_skey_, c_void_p),
                cast(ext_dll.cb_get_enc_keypair_, c_void_p),
                cast(ext_dll.cb_free_enc_keypair_, c_void_p),
                cast(ext_dll.cb_get_sign_keypair_, c_void_p),
                cast(ext_dll.cb_free_sign_keypair_, c_void_p),
                cast(byref(ctx), c_void_p) if ctx else None
            )
            return ext_loader, ext_dll, cbs

    # return handle
    def safe_handle_create(self, mode, dll_name=None, ctx=None):
        ext_loader, ext_dll, cbs = self.EncCallbacks.create(dll_name, ctx) if dll_name else (None, None, None)
        pimpl = ExtCallbackPtrWrapper(cbs, ext_loader, ext_dll)

        self.__target_dll.sge_safe_client_api_create.argtypes = [c_void_p, c_int]
        self.__target_dll.sge_safe_client_api_create.restype = c_void_p
        ptr = self.__target_dll.sge_safe_client_api_create(pimpl.cbs_byref(), mode)
        if ptr is None:
            raise StdError(-1, 'sge_safe_client_api_create failed')
        return pimpl.set_ptr(ptr)

    def safe_handle_destroy(self, pimpl):
        if not isinstance(pimpl, CallbackPtrWrapper):
            raise TypeError("pimpl must be CallbackPtrWrapper")
        if pimpl.ptr() is None:
            raise InvalidArgumentError("c-handle is None")

        self.__target_dll.sge_safe_client_api_destroy.argtypes = [c_void_p]
        self.__target_dll.sge_safe_client_api_destroy.restype = c_int
        ret = self.__target_dll.sge_safe_client_api_destroy(pimpl.ptr())
        pimpl.set_ptr(None)
        return self._check_success(ret, "sge_safe_client_api_destroy")

    ''' Safe Link Related '''

    # return link handle
    def safe_link_create(self, handle):
        if not isinstance(handle, CallbackPtrWrapper):
            raise TypeError("pimpl must be CallbackPtrWrapper")
        if handle.ptr() is None:
            raise InvalidArgumentError("c-handle is None")

        self.__target_dll.sge_safe_client_link_create.argtypes = [c_void_p]
        self.__target_dll.sge_safe_client_link_create.restype = c_void_p
        return self.__target_dll.sge_safe_client_link_create(handle.ptr())

    def safe_link_destroy(self, link):
        if link is None:
            raise InvalidArgumentError("c-link is None")

        self.__target_dll.sge_safe_client_link_destroy.argtypes = [c_void_p]
        self.__target_dll.sge_safe_client_link_destroy.restype = c_int
        ret = self.__target_dll.sge_safe_client_link_destroy(link)
        return self._check_success(ret, "sge_safe_client_link_destroy")

    def safe_link_reset(self, link):
        if link is None:
            raise InvalidArgumentError("c-link is None")

        self.__target_dll.sge_safe_client_link_reset.argtypes = [c_void_p]
        self.__target_dll.sge_safe_client_link_reset.restype = c_int
        ret = self.__target_dll.sge_safe_client_link_reset(link)
        return self._check_success(ret, "sge_safe_client_link_reset")

    def safe_link_reconnect(self, link):
        if link is None:
            raise InvalidArgumentError("c-link is None")

        self.__target_dll.sge_safe_client_link_reconnect.argtypes = [c_void_p]
        self.__target_dll.sge_safe_client_link_reconnect.restype = c_int
        ret = self.__target_dll.sge_safe_client_link_reconnect(link)
        return self._check_success(ret, "sge_safe_client_link_reconnect")

    def safe_link_is_connected(self, link):
        if link is None:
            raise InvalidArgumentError("c-link is None")

        self.__target_dll.sge_safe_client_link_is_connected.argtypes = [c_void_p]
        self.__target_dll.sge_safe_client_link_is_connected.restype = c_int
        ret = self.__target_dll.sge_safe_client_link_is_connected(link)
        return self._check_bool(ret, "sge_safe_client_link_is_connected")

    def safe_link_mode(self, link):
        if link is None:
            raise InvalidArgumentError("c-link is None")

        self.__target_dll.sge_safe_client_link_get_mode.argtypes = [c_void_p]
        self.__target_dll.sge_safe_client_link_get_mode.restype = c_int
        return self.__target_dll.sge_safe_client_link_get_mode(link)

    ''' Safe Session Related '''

    class ClientInfo(Structure):
        _fields_ = [
            ('sge_id', c_char * 16),
            ('trader_id', c_char * 16),
            ('enc_mode', c_int),
            ('svr_enc_pubkey', c_ubyte * 4096),
            ('svr_enc_pubkey_len', c_uint),
            ('svr_sign_pubkey', c_ubyte * 4096),
            ('svr_sign_pubkey_len', c_uint),
        ]

    # return session handle
    def safe_session_create(self, handle, svr_id, trader_id, enc_mode, svr_enc_pubkey, svr_sign_pubkey):
        if not isinstance(handle, CallbackPtrWrapper):
            raise TypeError("pimpl must be CallbackPtrWrapper")
        if handle.ptr() is None:
            raise InvalidArgumentError("c-handle is None")
        if not isinstance(svr_id, str):
            raise TypeError("svrId must be str")
        if not isinstance(trader_id, str):
            raise TypeError("traderId must be str")
        if not isinstance(svr_enc_pubkey, bytes):
            raise TypeError("svrEncPubKey must be bytes")
        if not isinstance(svr_sign_pubkey, bytes):
            raise TypeError("svrSignPubKey must be bytes")

        info = CSafeApi.ClientInfo(
            svr_id.encode(),
            trader_id.encode(),
            enc_mode,
            (c_ubyte * 4096)(*svr_enc_pubkey),
            len(svr_enc_pubkey),
            (c_ubyte * 4096)(*svr_sign_pubkey),
            len(svr_sign_pubkey),
        )

        self.__target_dll.sge_safe_create_client_session.argtypes = [c_void_p, c_void_p]
        self.__target_dll.sge_safe_create_client_session.restype = c_void_p
        return self.__target_dll.sge_safe_create_client_session(handle.ptr(), byref(info))

    def safe_session_destroy(self, session):
        if session is None:
            raise InvalidArgumentError("c-handle is None")

        self.__target_dll.sge_safe_free_client_session.argtypes = [c_void_p]
        self.__target_dll.sge_safe_free_client_session.restype = c_int
        ret = self.__target_dll.sge_safe_free_client_session(session)
        return self._check_success(ret, "sge_safe_free_client_session")

    class ClientHello(Structure):
        _fields_ = [
            ('trader_id', c_char * 16),
            ('sge_id', c_char * 16),
            ('enc_mode', c_longlong),
            ('safe_api_version', c_char * 128),
        ]

    def safe_session_create_client_hello(self, session):
        if session is None:
            raise InvalidArgumentError("c-handle is None")

        hello = CSafeApi.ClientHello()
        self.__target_dll.sge_safe_client_create_hello.argtypes = [c_void_p]
        self.__target_dll.sge_safe_client_create_hello.restype = c_int
        ret = self.__target_dll.sge_safe_client_create_hello(session, byref(hello))
        self._check_success(ret, "sge_safe_client_create_hello")
        return Utility.bytes_to_string(hello.trader_id), \
            Utility.bytes_to_string(hello.sge_id), \
            hello.enc_mode, \
            Utility.bytes_to_string(hello.safe_api_version)

    class ServerHello(Structure):
        _fields_ = [
            ('trader_id', c_char * 16),
            ('sge_id', c_char * 16),
            ('sge_random', c_char * 64),
            ('enc_mode', c_longlong),
        ]

    def safe_session_handle_server_hello(self, session, link, trader_id, svr_id, svr_random, enc_mode):
        if session is None:
            raise InvalidArgumentError("session c-handle is None")
        if not isinstance(trader_id, str):
            raise TypeError("traderId must be str")
        if not isinstance(svr_id, str):
            raise TypeError("svr_id must be str")
        if not isinstance(svr_random, str):
            raise TypeError("svrRandom must be str")
        if not isinstance(enc_mode, int):
            raise TypeError("encMode must be int")

        hello = CSafeApi.ServerHello(
            trader_id.encode(),
            svr_id.encode(),
            svr_random.encode(),
            enc_mode
        )
        self.__target_dll.sge_safe_client_handle_hello_rsp.argtypes = [c_void_p, c_void_p, c_void_p]
        self.__target_dll.sge_safe_client_handle_hello_rsp.restype = c_int
        ret = self.__target_dll.sge_safe_client_handle_hello_rsp(session, link, byref(hello))
        return self._check_success(ret, "sge_safe_client_handle_hello_rsp")

    class AuthenticationInfo(Structure):
        _fields_ = [
            ('trader_id', c_char * 16),
            ('sge_id', c_char * 16),
            ('trader_random', c_char * 64),
            ('sge_random', c_char * 64),
            ('signed_data', c_char * 256),
        ]

    def safe_session_create_authentication(self, session, link):
        if session is None:
            raise InvalidArgumentError("session c-handle is None")

        auth = CSafeApi.AuthenticationInfo()
        self.__target_dll.sge_safe_client_create_authentication.argtypes = [c_void_p, c_void_p, c_void_p]
        self.__target_dll.sge_safe_client_create_authentication.restype = c_int
        ret = self.__target_dll.sge_safe_client_create_authentication(session, link, byref(auth))
        self._check_success(ret, "sge_safe_client_create_authentication")
        return Utility.bytes_to_string(auth.trader_id), \
            Utility.bytes_to_string(auth.sge_id), \
            Utility.bytes_to_string(auth.trader_random), \
            Utility.bytes_to_string(auth.sge_random), \
            Utility.bytes_to_string(auth.signed_data)

    class SessionKey(Structure):
        _fields_ = [
            ('trader_id', c_char * 16),
            ('sge_id', c_char * 16),
            ('cipher_key', c_char * 256),
            ('cipher_random', c_char * 128),
            ('signed_data', c_char * 256),
        ]

    def safe_session_accept_session_key(self, session, link, trader_id, svr_id, cipher_key, cipher_random, signed_data):
        if session is None:
            raise InvalidArgumentError("session c-handle is None")
        if not isinstance(trader_id, str):
            raise TypeError("traderId must be str")
        if not isinstance(svr_id, str):
            raise TypeError("svrId must be str")
        if not isinstance(cipher_key, str):
            raise TypeError("cipherKey must be str")
        if not isinstance(cipher_random, str):
            raise TypeError("cipherRandom must be str")
        if not isinstance(signed_data, str):
            raise TypeError("signedData must be str")

        skey = CSafeApi.SessionKey(
            trader_id.encode(),
            svr_id.encode(),
            cipher_key.encode(),
            cipher_random.encode(),
            signed_data.encode()
        )
        self.__target_dll.sge_safe_client_accept_sessionkey.argtypes = [c_void_p, c_void_p, c_void_p]
        self.__target_dll.sge_safe_client_accept_sessionkey.restype = c_int
        ret = self.__target_dll.sge_safe_client_accept_sessionkey(session, link,
                                                                  string_at(addressof(skey), sizeof(skey)))
        return self._check_success(ret, "sge_safe_client_accept_sessionkey")

    def safe_session_encrypt(self, session, link, plain):
        if session is None:
            raise InvalidArgumentError("session c-handle is None")
        if not isinstance(plain, bytes):
            raise TypeError("plain must be bytes")

        buflen = c_int(len(plain) + self.CIPHER_BLOCK_LEN)
        buf = create_string_buffer(buflen.value)
        ret = self.c_encrypt(session, link, cast(plain, c_void_p), len(plain), buf, byref(buflen))
        self._check_success(ret, "sge_safe_client_packet_encrypt")
        return buf[:buflen.value]

    def safe_session_decrypt(self, session, link, cipher):
        if session is None:
            raise InvalidArgumentError("session c-handle is None")
        if not isinstance(cipher, bytes):
            raise TypeError("cipher must be bytes")

        buflen = c_int(len(cipher))
        buf = create_string_buffer(buflen.value)
        ret = self.c_decrypt(session, link, cast(cipher, c_void_p), len(cipher), buf, byref(buflen))
        self._check_success(ret, "sge_safe_client_packet_decrypt")
        return buf[:buflen.value]

    def safe_session_current_real_link(self, session, link):
        if session is None:
            raise InvalidArgumentError("session c-handle is None")

        self.__target_dll.sge_safe_client_get_session_current_used_in_link.argtypes = [c_void_p, c_void_p]
        self.__target_dll.sge_safe_client_get_session_current_used_in_link.restype = c_void_p
        return self.__target_dll.sge_safe_client_get_session_current_used_in_link(session, link)

    ''' Version Related '''

    # return string
    def get_version(self):
        self.__target_dll.sge_safe_client_get_api_version.restype = c_char_p
        return Utility.bytes_to_string(self.__target_dll.sge_safe_client_get_api_version())


class CSafeApiInstance:
    __instance = None

    @staticmethod
    def get():
        if CSafeApiInstance.__instance is None:
            CSafeApiInstance.__instance = CSafeApi()
        return CSafeApiInstance.__instance
